﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bamboo.Core;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;

namespace VietThinks.Model
{
    public class DomainRepository : IDisposable
    {
        protected DomainRepository(IRepository repository)
        {
            this.repository = repository;
        }

        private readonly IRepository repository;

        public static DomainRepository Open()
        {
            return DomainRepository.Open(OpenMode.UseSharedRepositoryIfAny);
        }

        public static DomainRepository Open(OpenMode openMode)
        {
            switch (openMode)
            {
                case OpenMode.AlwaysCreateSeparateRepository:
                    return DomainRepository.CreateRepository();

                case OpenMode.UseSharedRepositoryIfAny:
                default:
                    return RepositoryScope.ActiveRepository ?? DomainRepository.CreateRepository();
            }
        }

        private static DomainRepository CreateRepository()
        {
            var repositoryFactory = ServiceProvider.Instance.GetService<IRepositoryFactory>();

            Contract.Assert(repositoryFactory != null);

            return new DomainRepository(repositoryFactory.CreateRepository());
        }

        public void Close()
        {
            this.Dispose();
        }

        #region IDisposable Members

        private bool disposed = false;

        public void Dispose()
        {
            if (RepositoryScope.IsOutOfScope(this))
            {
                if (!this.disposed)
                {
                    this.repository.Dispose();

                    this.disposed = true;
                }
            }
        }

        #endregion

        public TEntity Get<TEntity>(object id, params string[] propertiesToPrefetch)
            where TEntity : DomainEntity
        {
            return this.repository.Query<TEntity>(propertiesToPrefetch)
                .Where(e => e.Id == id)
                .FirstOrDefault();
        }

        public TEntity Get<TEntity>(object id)
            where TEntity : DomainEntity
        {
            return this.repository.Get<TEntity>(id);
        }

        public IList<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> filter)
            where TEntity : DomainEntity
        {
            return this.repository.Get(filter);
        }

        public IQueryable<TEntity> Query<TEntity>(params string[] propertiesToPrefetch)
            where TEntity : DomainEntity
        {
            return this.repository.Query<TEntity>(propertiesToPrefetch);
        }

        public IQueryable<TEntity> Query<TEntity>()
            where TEntity : DomainEntity
        {
            return this.repository.Query<TEntity>();
        }

        public object Add<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            RequiresAllRulesConformed(entity);

            return this.repository.Add(entity);
        }

        public void Update<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            RequiresAllRulesConformed(entity);

            this.repository.Update(entity);
        }

        public void AddOrUpdate<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            RequiresAllRulesConformed(entity);

            this.repository.AddOrUpdate(entity);
        }

        public void Remove<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            this.repository.Remove(entity);
        }

        public void Refresh<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            this.repository.Refresh(entity);
        }

        public void Stale<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            this.repository.Stale(entity);
        }

        public void ExecuteCommand(string command, params object[] parametter)
        {
            this.repository.ExecuteCommand(command, parametter);
        }

        public IUnitOfWork CreateUnitOfWork()
        {
            return this.repository.CreateUnitOfWork();
        }

        private static void RequiresAllRulesConformed<TEntity>(TEntity entity)
            where TEntity : DomainEntity
        {
            var brokenRules = entity.GetBrokenRules();

            if (brokenRules.Count > 0)
            {
                throw new RuleBrokenException(brokenRules);
            }
        }

        public enum OpenMode
        {
            UseSharedRepositoryIfAny,
            AlwaysCreateSeparateRepository
        }
    }

    public class RuleBrokenException : ApplicationException
    {
        public RuleBrokenException(IEnumerable<string> brokenRules)
        {
            this.BrokenRules = brokenRules;
        }

        public IEnumerable<string> BrokenRules { get; private set; }
    }
}
